home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / RTFReader.java < prev    next >
Text File  |  1998-06-30  |  46KB  |  1,648 lines

  1. /*
  2.  * @(#)RTFReader.java    1.2 97/11/19
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text.rtf;
  21.  
  22. import java.lang.*;
  23. import java.util.*;
  24. import java.io.*;
  25. import java.awt.Font;
  26. import java.awt.Color;
  27.  
  28. import com.sun.java.swing.text.*;
  29.  
  30. /**
  31.  * Takes a sequence of RTF tokens and text and appends the text
  32.  * described by the RTF to a StyledDocument (the <em>target</em>).
  33.  * The RTF is lexed
  34.  * from the character stream by the RTFParser which is this class's
  35.  * superclass.
  36.  *
  37.  * This class is an indirect subclass of OutputStream. It must be closed
  38.  * in order to guarantee that all of the text has been sent to
  39.  * the text acceptor.
  40.  *
  41.  *   @see RTFParser
  42.  *   @see java.io.OutputStream
  43.  */
  44. class RTFReader extends RTFParser
  45. {
  46.   /** The object to which the parsed text is sent. */
  47.   StyledDocument target;
  48.  
  49.   /** Miscellaneous information about the parser's state. This
  50.    *  dictionary is saved and restored when an RTF group begins
  51.    *  or ends. */
  52.   Dictionary parserState;   /* Current parser state */
  53.   /** This is the "dst" item from parserState. rtfDestination
  54.    *  is the current rtf destination. It is cached in an instance
  55.    *  variable for speed. */
  56.   Destination rtfDestination;
  57.   /** This holds the current document attributes. */
  58.   MutableAttributeSet documentAttributes;
  59.  
  60.   /** This Dictionary maps Integer font numbers to String font names. */
  61.   Dictionary fontTable;
  62.   /** This array maps color indices to Color objects. */
  63.   Color[] colorTable;
  64.   /** This array maps character style numbers to Style objects. */
  65.   Style[] characterStyles;
  66.   /** This array maps paragraph style numbers to Style objects. */
  67.   Style[] paragraphStyles;
  68.   /** This array maps section style numbers to Style objects. */
  69.   Style[] sectionStyles;
  70.  
  71.   /** This is the RTF version number, extracted from the \rtf keyword.
  72.    *  The version information is currently not used. */
  73.   int rtfversion;
  74.  
  75.   /** <code>true</code> to indicate that if the next keyword is unknown,
  76.    *  the containing group should be ignored. */
  77.   boolean ignoreGroupIfUnknownKeyword;
  78.  
  79.   /** The parameter of the most recently parsed \\ucN keyword,
  80.    *  used for skipping alternative representations after a
  81.    *  Unicode character. */
  82.   int skippingCharacters;  
  83.  
  84.   
  85.   static private Dictionary straightforwardAttributes;
  86.   static {
  87.       straightforwardAttributes = RTFAttributes.attributesByKeyword();
  88.   }
  89.  
  90.   private MockAttributeSet mockery;
  91.  
  92.   /* this should be final, but there's a bug in javac... */
  93.   /** textKeywords maps RTF keywords to single-character strings,
  94.    *  for those keywords which simply insert some text. */
  95.   static Dictionary textKeywords = null;
  96.   static {
  97.       textKeywords = new Hashtable();
  98.       textKeywords.put("\\",         "\\");
  99.       textKeywords.put("{",          "{");
  100.       textKeywords.put("}",          "}");
  101.       textKeywords.put(" ",          "\u00A0");  /* not in the spec... */
  102.       textKeywords.put("~",          "\u00A0");  /* nonbreaking space */
  103.       textKeywords.put("_",          "\u2011");  /* nonbreaking hyphen */
  104.       textKeywords.put("bullet",     "\u2022");
  105.       textKeywords.put("emdash",     "\u2014");
  106.       textKeywords.put("emspace",    "\u2003");
  107.       textKeywords.put("endash",     "\u2013");
  108.       textKeywords.put("enspace",    "\u2002");
  109.       textKeywords.put("ldblquote",  "\u201C");
  110.       textKeywords.put("lquote",     "\u2018");
  111.       textKeywords.put("ltrmark",    "\u200E");
  112.       textKeywords.put("rdblquote",  "\u201D");
  113.       textKeywords.put("rquote",     "\u2019");
  114.       textKeywords.put("rtlmark",    "\u200F");
  115.       textKeywords.put("tab",        "\u0009");
  116.       textKeywords.put("zwj",        "\u200D");
  117.       textKeywords.put("zwnj",       "\u200C");
  118.       
  119.       /* There is no Unicode equivalent to an optional hyphen, as far as
  120.      I can tell. */
  121.       textKeywords.put("-",          "\u2027");  /* TODO: optional hyphen */
  122.   }
  123.  
  124.   /* some entries in parserState */
  125.   static final String TabAlignmentKey = "tab_alignment";
  126.   static final String TabLeaderKey = "tab_leader";
  127.     
  128.   static Dictionary characterSets;
  129.   static public boolean useNeXTForAnsi = false;
  130.   static {
  131.       characterSets = new Hashtable();
  132.   }
  133.  
  134. /* TODO: per-font font encodings ( \fcharset control word ) ? */
  135.  
  136. /**
  137.  * Creates a new RTFReader instance. Text will be sent to
  138.  * the specified TextAcceptor.
  139.  *
  140.  * @param destination The TextAcceptor which is to receive the text.
  141.  */
  142. public RTFReader(StyledDocument destination)
  143. {
  144.     int i;
  145.  
  146.     target = destination;
  147.     parserState = new Hashtable();
  148.     fontTable = new Hashtable();
  149.  
  150.     rtfversion = -1;
  151.  
  152.     mockery = new MockAttributeSet();
  153.     documentAttributes = new SimpleAttributeSet();
  154. }
  155.  
  156. /** Called when the RTFParser encounters a bin keyword in the
  157.  *  RTF stream.
  158.  *
  159.  *  @see RTFParser
  160.  */
  161. public void handleBinaryBlob(byte[] data)
  162. {
  163.     if (skippingCharacters > 0) {
  164.     /* a blob only counts as one character for skipping purposes */
  165.     skippingCharacters --;
  166.     return;
  167.     }
  168.  
  169.     /* someday, someone will want to do something with blobs */
  170. }
  171.  
  172.  
  173. /**
  174.  * Handles any pure text (containing no control characters) in the input
  175.  * stream. Called by the superclass. */
  176. public void handleText(String text)
  177. {
  178.     if (skippingCharacters > 0) {
  179.     if (skippingCharacters >= text.length()) {
  180.         skippingCharacters -= text.length();
  181.         return;
  182.     } else {
  183.         text = text.substring(skippingCharacters);
  184.         skippingCharacters = 0;
  185.     }
  186.     }
  187.  
  188.     if (rtfDestination != null) {
  189.     rtfDestination.handleText(text);
  190.     return;
  191.     }
  192.  
  193.     warnings.println("Text with no destination. oops.");
  194. }
  195.  
  196. /** The default color for text which has no specified color. */
  197. Color defaultColor()
  198. {
  199.     return Color.black;
  200. }
  201.  
  202. /** Called by the superclass when a new RTF group is begun.
  203.  *  This implementation saves the current parserState, and gives
  204.  *  the current destination a chence to save its own state.
  205.  * @see RTFParser#begingroup
  206.  */
  207. public void begingroup()
  208. {
  209. //    warnings.println("--- begingroup ---");
  210.  
  211.     if (skippingCharacters > 0) {
  212.     /* TODO this indicates an error in the RTF. Log it? */
  213.     skippingCharacters = 0;
  214.     }
  215.  
  216.     /* we do this little dance to avoid cloning the entire state stack and
  217.        immediately throwing it away. */
  218.     Object oldSaveState = parserState.get("_savedState");
  219.     if (oldSaveState != null)
  220.     parserState.remove("_savedState");
  221.     Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
  222.     if (oldSaveState != null)
  223.     saveState.put("_savedState", oldSaveState);
  224.     parserState.put("_savedState", saveState);
  225.  
  226.     if (rtfDestination != null)
  227.     rtfDestination.begingroup();
  228. }
  229.  
  230. /** Called by the superclass when the current RTF group is closed.
  231.  *  This restores the parserState saved by <code>begingroup()</code>
  232.  *  as well as invoking the endgroup method of the current
  233.  *  destination.
  234.  * @see RTFParser#endgroup
  235.  */
  236. public void endgroup()
  237. {
  238. //    warnings.println("--- endgroup, level=" + level + " ---");
  239.  
  240.     if (skippingCharacters > 0) {
  241.     /* NB this indicates an error in the RTF. Log it? */
  242.     skippingCharacters = 0;
  243.     }
  244.  
  245.     Dictionary restoredState = (Dictionary)parserState.get("_savedState");
  246.     Destination restoredDestination = (Destination)restoredState.get("dst");
  247.     if (restoredDestination != rtfDestination) {
  248.     rtfDestination.close(); /* allow the destination to clean up */
  249.     rtfDestination = restoredDestination;
  250.     }
  251.     Dictionary oldParserState = parserState;
  252.     parserState = restoredState;
  253.     if (rtfDestination != null)
  254.     rtfDestination.endgroup(oldParserState);
  255. }
  256.  
  257. protected void setRTFDestination(Destination newDestination)
  258. {
  259.     /* Check that setting the destination won't close the
  260.        current destination (should never happen) */
  261.     Dictionary previousState = (Dictionary)parserState.get("_savedState");
  262.     if (previousState != null) {
  263.     if (rtfDestination != previousState.get("dst")) {
  264.         warnings.println("Warning, RTF destination overridden, invalid RTF.");
  265.         rtfDestination.close();
  266.     }
  267.     }
  268.     rtfDestination = newDestination;
  269.     parserState.put("dst", rtfDestination);
  270. }
  271.  
  272. /** Called by the user when there is no more input (<i>i.e.</i>,
  273.  * at the end of the RTF file.)
  274.  *
  275.  * @see OutputStream#close
  276.  */
  277. public void close()
  278.     throws IOException
  279. {
  280.     Enumeration docProps = documentAttributes.getAttributeNames();
  281.     while(docProps.hasMoreElements()) {
  282.         Object propName = docProps.nextElement();
  283.     target.putProperty(propName,
  284.                documentAttributes.getAttribute((String)propName));
  285.     }
  286.  
  287.     /* RTFParser should have ensured that all our groups are closed */
  288.  
  289.     warnings.println("RTF filter done.");
  290.  
  291.     super.close();
  292. }
  293.  
  294. /**
  295.  * Handles a parameterless RTF keyword. This is called by the superclass
  296.  * (RTFParser) when a keyword is found in the input stream.
  297.  *
  298.  * @returns <code>true</code> if the keyword is recognized and handled;
  299.  *          <code>false</code> otherwise
  300.  * @see RTFParser#handleKeyword
  301.  */
  302. public boolean handleKeyword(String keyword)
  303. {
  304.     Object item;
  305.     boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  306.  
  307.     if (skippingCharacters > 0) {
  308.     skippingCharacters --;
  309.     return true;
  310.     }
  311.  
  312.     ignoreGroupIfUnknownKeyword = false;
  313.     
  314.     if ((item = textKeywords.get(keyword)) != null) {
  315.     handleText((String)item);
  316.     return true;
  317.     }
  318.     
  319.     if (keyword.equals("fonttbl")) {
  320.     setRTFDestination(new FonttblDestination());
  321.         return true;
  322.     }
  323.  
  324.     if (keyword.equals("colortbl")) {
  325.     setRTFDestination(new ColortblDestination());
  326.         return true;
  327.     }
  328.  
  329.     if (keyword.equals("stylesheet")) {
  330.     setRTFDestination(new StylesheetDestination());
  331.         return true;
  332.     }
  333.  
  334.     if (keyword.equals("info")) {
  335.     setRTFDestination(new InfoDestination());
  336.     return false; 
  337.     }
  338.  
  339.     if (keyword.equals("mac")) {
  340.     setCharacterSet("mac");
  341.         return true;
  342.     }
  343.  
  344.     if (keyword.equals("ansi")) {
  345.     if (useNeXTForAnsi)
  346.         setCharacterSet("NeXT");
  347.     else
  348.         setCharacterSet("ansi");
  349.         return true;
  350.     }
  351.  
  352.     if (keyword.equals("next")) {
  353.     setCharacterSet("NeXT");
  354.     return true;
  355.     }
  356.  
  357.     if (keyword.equals("pc")) {
  358.     setCharacterSet("cpg437"); /* IBM Code Page 437 */
  359.         return true;
  360.     }
  361.  
  362.     if (keyword.equals("pca")) {
  363.     setCharacterSet("cpg850"); /* IBM Code Page 850 */
  364.         return true;
  365.     }
  366.  
  367.     if (keyword.equals("*")) {
  368.         ignoreGroupIfUnknownKeyword = true;
  369.         return true;
  370.     }
  371.  
  372.     if (rtfDestination != null) {
  373.     if(rtfDestination.handleKeyword(keyword))
  374.         return true;
  375.     }
  376.  
  377.     /* this point is reached only if the keyword is unrecognized */
  378.  
  379.     /* other destinations we don't understand and therefore ignore */
  380.     if (keyword.equals("aftncn") ||
  381.     keyword.equals("aftnsep") ||
  382.     keyword.equals("aftnsepc") ||
  383.     keyword.equals("annotation") ||
  384.     keyword.equals("atnauthor") ||
  385.     keyword.equals("atnicn") ||
  386.     keyword.equals("atnid") ||
  387.     keyword.equals("atnref") ||
  388.     keyword.equals("atntime") ||
  389.     keyword.equals("atrfend") ||
  390.     keyword.equals("atrfstart") ||
  391.     keyword.equals("bkmkend") ||
  392.     keyword.equals("bkmkstart") ||
  393.     keyword.equals("datafield") ||
  394.     keyword.equals("do") ||
  395.     keyword.equals("dptxbxtext") ||
  396.     keyword.equals("falt") ||
  397.     keyword.equals("field") ||
  398.     keyword.equals("file") ||
  399.     keyword.equals("filetbl") ||
  400.     keyword.equals("fname") ||
  401.     keyword.equals("fontemb") ||
  402.     keyword.equals("fontfile") ||
  403.     keyword.equals("footer") ||
  404.     keyword.equals("footerf") ||
  405.     keyword.equals("footerl") ||
  406.     keyword.equals("footerr") ||
  407.     keyword.equals("footnote") ||
  408.     keyword.equals("ftncn") ||
  409.     keyword.equals("ftnsep") ||
  410.     keyword.equals("ftnsepc") ||
  411.     keyword.equals("header") ||
  412.     keyword.equals("headerf") ||
  413.     keyword.equals("headerl") ||
  414.     keyword.equals("headerr") ||
  415.     keyword.equals("keycode") ||
  416.     keyword.equals("nextfile") ||
  417.     keyword.equals("object") ||
  418.     keyword.equals("pict") ||
  419.     keyword.equals("pn") ||
  420.     keyword.equals("pnseclvl") ||
  421.     keyword.equals("pntxtb") ||
  422.     keyword.equals("pntxta") ||
  423.     keyword.equals("revtbl") ||
  424.     keyword.equals("rxe") ||
  425.     keyword.equals("tc") ||
  426.     keyword.equals("template") ||
  427.     keyword.equals("txe") ||
  428.     keyword.equals("xe")) {
  429.     ignoreGroupIfUnknownKeywordSave = true;
  430.     }
  431.  
  432.     if (ignoreGroupIfUnknownKeywordSave) {
  433.     setRTFDestination(new DiscardingDestination());
  434.     }
  435.  
  436.     return false;
  437. }
  438.  
  439. /**
  440.  * Handles an RTF keyword and its integer parameter. 
  441.  * This is called by the superclass
  442.  * (RTFParser) when a keyword is found in the input stream.
  443.  *
  444.  * @returns <code>true</code> if the keyword is recognized and handled;
  445.  *          <code>false</code> otherwise
  446.  * @see RTFParser#handleKeyword
  447.  */
  448. public boolean handleKeyword(String keyword, int parameter)
  449. {
  450.     boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  451.  
  452.     if (skippingCharacters > 0) {
  453.     skippingCharacters --;
  454.     return true;
  455.     }
  456.  
  457.     ignoreGroupIfUnknownKeyword = false;
  458.  
  459.     if (keyword.equals("uc")) {
  460.     /* count of characters to skip after a unicode character */
  461.     parserState.put("UnicodeSkip", new Integer(parameter));
  462.     return true;
  463.     }
  464.     if (keyword.equals("u")) {
  465.     if (parameter < 0)
  466.         parameter = parameter + 65536;
  467.     handleText((char)parameter);
  468.     Number skip = (Number)(parserState.get("UnicodeSkip"));
  469.     if (skip != null) {
  470.         skippingCharacters = skip.intValue();
  471.     } else {
  472.         skippingCharacters = 1;
  473.     }
  474.     return true;
  475.     }
  476.  
  477.     if (keyword.equals("rtf")) {
  478.         rtfversion = parameter;
  479.     setRTFDestination(new DocumentDestination());
  480.         return true;
  481.     }
  482.  
  483.     if (keyword.startsWith("NeXT") ||
  484.     keyword.equals("private"))
  485.     ignoreGroupIfUnknownKeywordSave = true;
  486.  
  487.     if (rtfDestination != null) {
  488.     if(rtfDestination.handleKeyword(keyword, parameter))
  489.         return true;
  490.     }
  491.  
  492.     /* this point is reached only if the keyword is unrecognized */
  493.  
  494.     if (ignoreGroupIfUnknownKeywordSave) {
  495.     setRTFDestination(new DiscardingDestination());
  496.     }
  497.  
  498.     return false;
  499. }
  500.  
  501. private void setTargetAttribute(String name, Object value)
  502. {
  503. //    target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
  504. }
  505.  
  506. /**
  507.  * setCharacterSet sets the current translation table to correspond with
  508.  * the named character set. The character set is loaded if necessary.
  509.  *
  510.  * @see AbstractFilter
  511.  */
  512. public void setCharacterSet(String name)
  513. {
  514.     Object set;
  515.  
  516.     try {
  517.         set = getCharacterSet(name);
  518.     } catch (Exception e) {
  519.     warnings.println("Exception loading RTF character set \"" + name + "\": " + e);
  520.     set = null;
  521.     }
  522.  
  523.     if (set != null) {
  524.     translationTable = (char[])set;
  525.     } else {
  526.     warnings.println("Unknown RTF character set \"" + name + "\"");
  527.     if (!name.equals("ansi")) {
  528.         try {
  529.         translationTable = (char[])getCharacterSet("ansi");
  530.         } catch (IOException e) {
  531.         throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")");
  532.         }
  533.     }
  534.     }
  535.  
  536.     setTargetAttribute(Constants.RTFCharacterSet, name);
  537. }
  538.  
  539. /** Adds a character set to the RTFReader's list
  540.  *  of known character sets */
  541. public static synchronized void 
  542. defineCharacterSet(String name, char[] table)
  543. {
  544.     if (table.length < 256)
  545.     throw new IllegalArgumentException("Translation table must have 256 entries.");
  546.  
  547.     characterSets.put(name, table);
  548. }
  549.  
  550. /** Looks up a named character set. A character set is a 256-entry
  551.  *  array of characters, mapping unsigned byte values to their Unicode
  552.  *  equivalents. The character set is loaded if necessary.
  553.  *
  554.  *  @returns the character set
  555.  */
  556. public static synchronized Object
  557. getCharacterSet(String name)
  558.     throws IOException
  559. {
  560.     char[] set;
  561.  
  562.     set = (char [])characterSets.get(name);
  563.     if (set == null) {
  564.       System.err.println("reading charset " + name);
  565.       InputStream charsetStream = null;
  566.       /* TODO: when class names are allowed as literal Class objects, 
  567.      change this to use the nicer syntax */
  568.       Class This;
  569.       try {
  570.       This = Class.forName("com.sun.java.swing.text.rtf.RTFReader");
  571.       } catch (ClassNotFoundException e) {
  572.       throw new InternalError("Help, I do not exist (" + e + ")");
  573.       }
  574.       charsetStream = This.getResourceAsStream("charsets/" + name + ".txt");
  575.       set = readCharset(charsetStream);
  576.       defineCharacterSet(name, set);
  577.     }
  578.     return set;
  579. }
  580.  
  581. /** Parses a character set from an InputStream. The character set
  582.  * must contain 256 decimal integers, separated by whitespace, with
  583.  * no punctuation. B- and C- style comments are allowed.
  584.  *
  585.  * @returns the newly read character set
  586.  */
  587. static char[] readCharset(InputStream strm)
  588.      throws IOException
  589. {
  590.     char[] values = new char[256];
  591.     int i;
  592.     StreamTokenizer in = new StreamTokenizer(new InputStreamReader(strm));
  593.  
  594.     in.eolIsSignificant(false);
  595.     in.commentChar('#');
  596.     in.slashSlashComments(true);
  597.     in.slashStarComments(true);
  598.  
  599.     i = 0;
  600.     while (i < 256) {
  601.     int ttype;
  602.     try {
  603.         ttype = in.nextToken();
  604.     } catch (Exception e) {
  605.         throw new IOException("Unable to read from character set file (" + e + ")");
  606.     }
  607.     if (ttype != in.TT_NUMBER) {
  608. //        System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
  609.         throw new IOException("Unexpected token in character set file");
  610. //        continue;
  611.     }
  612.     values[i] = (char)(in.nval);
  613.     i++;
  614.     }
  615.  
  616.     return values;
  617. }
  618.  
  619. static char[] readCharset(java.net.URL href)
  620.      throws IOException
  621. {
  622.     return readCharset(href.openStream());
  623. }
  624.  
  625. /** An interface (could be an entirely abstract class) describing
  626.  *  a destination. The RTF reader always has a current destination
  627.  *  which is where text is sent. 
  628.  *
  629.  *  @see RTFReader
  630.  */
  631. interface Destination {
  632.     void handleBinaryBlob(byte[] data);
  633.     void handleText(String text);
  634.     boolean handleKeyword(String keyword);
  635.     boolean handleKeyword(String keyword, int parameter);
  636.  
  637.     void begingroup();
  638.     void endgroup(Dictionary oldState);
  639.  
  640.     void close();
  641. }
  642.  
  643. /** This data-sink class is used to implement ignored destinations
  644.  *  (e.g. {\*\blegga blah blah blah} )
  645.  *  It accepts all keywords and text but does nothing with them. */
  646. class DiscardingDestination implements Destination
  647. {
  648.     public void handleBinaryBlob(byte[] data)
  649.     {
  650.     /* Discard binary blobs. */
  651.     }
  652.  
  653.     public void handleText(String text)
  654.     {
  655.     /* Discard text. */
  656.     }
  657.  
  658.     public boolean handleKeyword(String text)
  659.     {
  660.     /* Accept and discard keywords. */
  661.     return true;
  662.     }
  663.  
  664.     public boolean handleKeyword(String text, int parameter)
  665.     {
  666.     /* Accept and discard parameterized keywords. */
  667.     return true;
  668.     }
  669.  
  670.     public void begingroup()
  671.     {
  672.     /* Ignore groups --- the RTFReader will keep track of the
  673.        current group level as necessary */
  674.     }
  675.  
  676.     public void endgroup(Dictionary oldState)
  677.     {
  678.     /* Ignore groups */
  679.     }
  680.  
  681.     public void close()
  682.     {
  683.     /* No end-of-destination cleanup needed */
  684.     }
  685. }
  686.  
  687. /** Reads the fonttbl group, inserting fonts into the RTFReader's
  688.  *  fontTable dictionary. */
  689. class FonttblDestination implements Destination
  690. {
  691.     int nextFontNumber;
  692.     String nextFontFamily;
  693.     
  694.     public void handleBinaryBlob(byte[] data)
  695.     { /* Discard binary blobs. */ }
  696.  
  697.     /* TODO do these routines work correctly if a write buffer divides a
  698.        font name? (Probably not. Should allow for it as rare case) */
  699.     public void handleText(String text)
  700.     {
  701.         int semicolon = text.indexOf(';');
  702.         String fontName;
  703.         Object fontNum;  /* an Integer, but we don't care */
  704.  
  705.         if (semicolon > 0)
  706.             fontName = text.substring(0, semicolon);
  707.         else
  708.             fontName = text;
  709.  
  710.         /* TODO: do something with the font family. */
  711.  
  712.         fontTable.put(new Integer(nextFontNumber), fontName);
  713.  
  714.     nextFontNumber = -1;
  715.     nextFontFamily = null;
  716.         return;
  717.     }
  718.  
  719.     public boolean handleKeyword(String keyword)
  720.     {
  721.     if (keyword.charAt(0) == 'f') {
  722.         nextFontFamily = keyword.substring(1);
  723.         return true;
  724.     }
  725.     
  726.     return false;
  727.     }
  728.  
  729.     public boolean handleKeyword(String keyword, int parameter)
  730.     {
  731.     if (keyword.equals("f")) {
  732.         nextFontNumber = parameter;
  733.         return true;
  734.     }
  735.  
  736.     return false;
  737.     }
  738.  
  739.     /* Groups are irrelevant. */
  740.     public void begingroup() {}
  741.     public void endgroup(Dictionary oldState) {}
  742.  
  743.     /* currently, the only thing we do when the font table ends is
  744.        dump its contents to the debugging log. */
  745.     public void close()
  746.     {
  747.         Enumeration nums = fontTable.keys();
  748.         warnings.println("Done reading font table.");
  749.         while(nums.hasMoreElements()) {
  750.             Integer num = (Integer)nums.nextElement();
  751.             warnings.println("Number " + num + ": " + fontTable.get(num));
  752.         }
  753.         warnings.println();
  754.     }
  755. }
  756.  
  757. /** Reads the colortbl group. Upon end-of-group, the RTFReader's
  758.  *  color table is set to an array containing the read colors. */
  759. class ColortblDestination implements Destination
  760. {
  761.     int red, green, blue;
  762.     Vector proTemTable;
  763.  
  764.     public ColortblDestination()
  765.     {
  766.     red = 0;
  767.     green = 0;
  768.     blue = 0;
  769.     proTemTable = new Vector();
  770.     }
  771.  
  772.     public void handleText(String text)
  773.     {
  774.         int index = 0;
  775.  
  776.         for (index = 0; index < text.length(); index ++) {
  777.             if (text.charAt(index) == ';') {
  778.                 Color newColor;
  779.         newColor = new Color(red, green, blue);
  780.         proTemTable.addElement(newColor);
  781.             }
  782.         }
  783.     }
  784.  
  785.     public void close()
  786.     {
  787.     int count = proTemTable.size();
  788.         warnings.println("Done reading color table, " + count + " entries.");
  789.     colorTable = new Color[count];
  790.     proTemTable.copyInto(colorTable);
  791.     }
  792.  
  793.     public boolean handleKeyword(String keyword, int parameter)
  794.     {
  795.         if (keyword.equals("red"))
  796.         red = parameter;
  797.     else if (keyword.equals("green"))
  798.         green = parameter;
  799.     else if (keyword.equals("blue"))
  800.         blue = parameter;
  801.     else
  802.         return false;
  803.     
  804.     return true;
  805.     }
  806.  
  807.     /* Colortbls don't understand any parameterless keywords */
  808.     public boolean handleKeyword(String keyword) { return false; }
  809.  
  810.     /* Groups are irrelevant. */
  811.     public void begingroup() {}
  812.     public void endgroup(Dictionary oldState) {}
  813.  
  814.     /* Shouldn't see any binary blobs ... */
  815.     public void handleBinaryBlob(byte[] data) {}
  816. }
  817.  
  818. /** Handles the stylesheet keyword. Styles are read and sorted
  819.  *  into the three style arrays in the RTFReader. */
  820. class StylesheetDestination
  821.     extends DiscardingDestination
  822.     implements Destination
  823. {
  824.     Dictionary definedStyles;
  825.  
  826.     public StylesheetDestination()
  827.     {
  828.     definedStyles = new Hashtable();
  829.     }
  830.  
  831.     public void begingroup()
  832.     {
  833.     setRTFDestination(new StyleDefiningDestination());
  834.     }
  835.  
  836.     public void close() 
  837.     {
  838.         Vector chrStyles, pgfStyles, secStyles;
  839.     chrStyles = new Vector();
  840.     pgfStyles = new Vector();
  841.     secStyles = new Vector();
  842.     Enumeration styles = definedStyles.elements();
  843.     while(styles.hasMoreElements()) {
  844.         StyleDefiningDestination style;
  845.         Style defined;
  846.         style = (StyleDefiningDestination)styles.nextElement();
  847.         defined = style.realize();
  848.         warnings.println("Style "+style.number+" ("+style.styleName+"): "+defined);
  849.         String stype = (String)defined.getAttribute(Constants.StyleType);
  850.         Vector toSet;
  851.         if (stype.equals(Constants.STSection)) {
  852.             toSet = secStyles;
  853.         } else if (stype.equals(Constants.STCharacter)) {
  854.             toSet = chrStyles;
  855.         } else {
  856.             toSet = pgfStyles;
  857.         }
  858.         if (toSet.size() <= style.number)
  859.             toSet.setSize(style.number + 1);
  860.         toSet.setElementAt(defined, style.number);
  861.     }
  862.     if (!(chrStyles.isEmpty())) {
  863.         Style[] styleArray = new Style[chrStyles.size()];
  864.         chrStyles.copyInto(styleArray);
  865.         characterStyles = styleArray;
  866.     }
  867.     if (!(pgfStyles.isEmpty())) {
  868.         Style[] styleArray = new Style[pgfStyles.size()];
  869.         pgfStyles.copyInto(styleArray);
  870.         paragraphStyles = styleArray;
  871.     }
  872.     if (!(secStyles.isEmpty())) {
  873.         Style[] styleArray = new Style[secStyles.size()];
  874.         secStyles.copyInto(styleArray);
  875.         sectionStyles = styleArray;
  876.     }
  877.  
  878. /* (old debugging code)
  879.     int i, m;
  880.     if (characterStyles != null) {
  881.       m = characterStyles.length;
  882.       for(i=0;i<m;i++) 
  883.         warnings.println("chrStyle["+i+"]="+characterStyles[i]);
  884.     } else warnings.println("No character styles.");
  885.     if (paragraphStyles != null) {
  886.       m = paragraphStyles.length;
  887.       for(i=0;i<m;i++) 
  888.         warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
  889.     } else warnings.println("No paragraph styles.");
  890.     if (sectionStyles != null) {
  891.       m = characterStyles.length;
  892.       for(i=0;i<m;i++) 
  893.         warnings.println("secStyle["+i+"]="+sectionStyles[i]);
  894.     } else warnings.println("No section styles.");
  895. */
  896.     }
  897.  
  898.     /** This subclass handles an individual style */
  899.     class StyleDefiningDestination
  900.     extends AttributeTrackingDestination
  901.     implements Destination
  902.     {
  903.     final int STYLENUMBER_NONE = 222;  /* I (heart) Microsoft */
  904.     boolean additive;
  905.     boolean characterStyle;
  906.     boolean sectionStyle;
  907.     public String styleName;
  908.     public int number;
  909.     int basedOn;
  910.     int nextStyle;
  911.     boolean hidden;
  912.  
  913.     Style realizedStyle;
  914.  
  915.     public StyleDefiningDestination()
  916.     {
  917.         additive = false;
  918.         characterStyle = false;
  919.         sectionStyle = false;
  920.         styleName = null;
  921.         number = 0;
  922.         basedOn = STYLENUMBER_NONE;
  923.         nextStyle = STYLENUMBER_NONE;
  924.         hidden = false;
  925.     }
  926.     
  927.     public void handleText(String text)
  928.     {
  929.         if (styleName != null)
  930.         styleName = styleName + text;
  931.         else
  932.         styleName = text;
  933.     }
  934.  
  935.     public void close() {
  936.         int semicolon = styleName.indexOf(';');
  937.         if (semicolon > 0)
  938.         styleName = styleName.substring(0, semicolon);
  939.         definedStyles.put(new Integer(number), this);
  940.         super.close();
  941.     }
  942.  
  943.     public boolean handleKeyword(String keyword)
  944.     {
  945.         if (keyword.equals("additive")) {
  946.         additive = true;
  947.         return true;
  948.         }
  949.         if (keyword.equals("shidden")) {
  950.         hidden = true;
  951.         return true;
  952.         }
  953.         return super.handleKeyword(keyword);
  954.     }
  955.  
  956.     public boolean handleKeyword(String keyword, int parameter)
  957.     {
  958.         if (keyword.equals("s")) {
  959.         characterStyle = false;
  960.         sectionStyle = false;
  961.         number = parameter;
  962.         } else if (keyword.equals("cs")) {
  963.         characterStyle = true;
  964.         sectionStyle = false;
  965.         number = parameter;
  966.         } else if (keyword.equals("ds")) {
  967.         characterStyle = false;
  968.         sectionStyle = true;
  969.         number = parameter;
  970.         } else if (keyword.equals("sbasedon")) {
  971.         basedOn = parameter;
  972.         } else if (keyword.equals("snext")) {
  973.         nextStyle = parameter;
  974.         } else {
  975.         return super.handleKeyword(keyword, parameter);
  976.         }
  977.         return true;
  978.     }
  979.  
  980.     public Style realize()
  981.     {
  982.         Style basis = null;
  983.         Style next = null;
  984.         
  985.         if (realizedStyle != null)
  986.         return realizedStyle;
  987.  
  988.         if (basedOn != STYLENUMBER_NONE) {
  989.         StyleDefiningDestination styleDest;
  990.         styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(basedOn));
  991.         if (styleDest != null) {
  992.             basis = styleDest.realize();
  993.         }
  994.         }
  995.  
  996.         /* NB: Swing StyleContext doesn't allow distinct styles with
  997.            the same name; RTF apparently does. This may confuse the
  998.            user. */
  999.         realizedStyle = target.addStyle(styleName, basis);
  1000.  
  1001.         if (characterStyle) {
  1002.         realizedStyle.addAttributes(currentTextAttributes());
  1003.         realizedStyle.addAttribute(Constants.StyleType,
  1004.                        Constants.STCharacter);
  1005.         } else if (sectionStyle) {
  1006.         realizedStyle.addAttributes(currentSectionAttributes());
  1007.             realizedStyle.addAttribute(Constants.StyleType,
  1008.                        Constants.STSection);
  1009.         } else { /* must be a paragraph style */
  1010.         realizedStyle.addAttributes(currentParagraphAttributes());
  1011.             realizedStyle.addAttribute(Constants.StyleType,
  1012.                        Constants.STParagraph);
  1013.         }
  1014.  
  1015.         if (nextStyle != STYLENUMBER_NONE) {
  1016.         StyleDefiningDestination styleDest;
  1017.         styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(nextStyle));
  1018.         if (styleDest != null) {
  1019.             next = styleDest.realize();
  1020.         }
  1021.         }
  1022.  
  1023.         if (next != null)
  1024.         realizedStyle.addAttribute(Constants.StyleNext, next);
  1025.         realizedStyle.addAttribute(Constants.StyleAdditive,
  1026.                        new Boolean(additive));
  1027.         realizedStyle.addAttribute(Constants.StyleHidden,
  1028.                        new Boolean(hidden));
  1029.  
  1030.         return realizedStyle;
  1031.     }
  1032.     }
  1033. }
  1034.  
  1035. /** Handles the info group. Currently no info keywords are recognized
  1036.  *  so this is a subclass of DiscardingDestination. */
  1037. class InfoDestination
  1038.     extends DiscardingDestination
  1039.     implements Destination
  1040. {
  1041. }
  1042.  
  1043. /** RTFReader.TextHandlingDestination is an abstract RTF destination
  1044.  *  which simply tracks the attributes specified by the RTF control words
  1045.  *  in internal form and can produce acceptable AttributeSets for the
  1046.  *  current character, paragraph, and section attributes. It is up
  1047.  *  to the subclasses to determine what is done with the actual text. */
  1048. abstract class AttributeTrackingDestination implements Destination
  1049. {
  1050.     /** This is the "chr" element of parserState, cached for
  1051.      *  more efficient use */
  1052.     MutableAttributeSet characterAttributes;
  1053.     /** This is the "pgf" element of parserState, cached for
  1054.      *  more efficient use */
  1055.     MutableAttributeSet paragraphAttributes;
  1056.     /** This is the "sec" element of parserState, cached for
  1057.      *  more efficient use */
  1058.     MutableAttributeSet sectionAttributes;
  1059.     
  1060.     public AttributeTrackingDestination()
  1061.     {
  1062.     characterAttributes = rootCharacterAttributes();
  1063.     parserState.put("chr", characterAttributes);
  1064.     paragraphAttributes = rootParagraphAttributes();
  1065.     parserState.put("pgf", paragraphAttributes);
  1066.     sectionAttributes = rootSectionAttributes();
  1067.     parserState.put("sec", sectionAttributes);
  1068.     }
  1069.  
  1070.     abstract public void handleText(String text);
  1071.  
  1072.     public void handleBinaryBlob(byte[] data)
  1073.     {
  1074.         /* This should really be in TextHandlingDestination, but
  1075.      * since *nobody* does anything with binary blobs, this
  1076.      * is more convenient. */
  1077.     warnings.println("Unexpected binary data in RTF file.");
  1078.     }
  1079.  
  1080.     public void begingroup()
  1081.     {
  1082.     AttributeSet characterParent = currentTextAttributes();
  1083.     AttributeSet paragraphParent = currentParagraphAttributes();
  1084.     AttributeSet sectionParent = currentSectionAttributes();
  1085.  
  1086.     /* It would probably be more efficient to use the 
  1087.      * resolver property of the attributes set for
  1088.      * implementing rtf groups,
  1089.      * but that's needed for styles. */
  1090.  
  1091.     /* update the cached attribute dictionaries */
  1092.     characterAttributes = new SimpleAttributeSet();
  1093.     characterAttributes.addAttributes(characterParent);
  1094.     parserState.put("chr", characterAttributes);
  1095.  
  1096.     paragraphAttributes = new SimpleAttributeSet();
  1097.     paragraphAttributes.addAttributes(paragraphParent);
  1098.     parserState.put("pgf", paragraphAttributes);
  1099.  
  1100.     sectionAttributes = new SimpleAttributeSet();
  1101.     sectionAttributes.addAttributes(sectionParent);
  1102.     parserState.put("sec", sectionAttributes);
  1103.     }
  1104.  
  1105.     public void endgroup(Dictionary oldState)
  1106.     {
  1107.     characterAttributes = (MutableAttributeSet)parserState.get("chr");
  1108.     paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
  1109.     sectionAttributes   = (MutableAttributeSet)parserState.get("sec");
  1110.     }
  1111.  
  1112.     public void close()
  1113.     {
  1114.     }
  1115.  
  1116.     public boolean handleKeyword(String keyword)
  1117.     {
  1118.     if (keyword.equals("ulnone")) {
  1119.         return handleKeyword("ul", 0);
  1120.     }
  1121.     
  1122.     {
  1123.         Object item = straightforwardAttributes.get(keyword);
  1124.         if (item != null) {
  1125.             RTFAttribute attr = (RTFAttribute)item;
  1126.         boolean ok;
  1127.         
  1128.         switch(attr.domain()) {
  1129.           case RTFAttribute.D_CHARACTER:
  1130.             ok = attr.set(characterAttributes);
  1131.             break;
  1132.           case RTFAttribute.D_PARAGRAPH:
  1133.             ok = attr.set(paragraphAttributes);
  1134.             break;
  1135.           case RTFAttribute.D_SECTION:
  1136.             ok = attr.set(sectionAttributes);
  1137.             break;
  1138.           case RTFAttribute.D_META:
  1139.             mockery.backing = parserState;
  1140.             ok = attr.set(mockery);
  1141.             mockery.backing = null;
  1142.             break;
  1143.           case RTFAttribute.D_DOCUMENT:
  1144.             ok = attr.set(documentAttributes);
  1145.             break;
  1146.           default:
  1147.             /* should never happen */
  1148.             ok = false;
  1149.             break;
  1150.         }
  1151.         if (ok)
  1152.             return true;
  1153.         }
  1154.     }
  1155.  
  1156.     
  1157.     if (keyword.equals("plain")) {
  1158.         resetCharacterAttributes();
  1159.         return true;
  1160.     }
  1161.     
  1162.     if (keyword.equals("pard")) {
  1163.         resetParagraphAttributes();
  1164.         return true;
  1165.     }
  1166.  
  1167.     if (keyword.equals("sectd")) {
  1168.         resetSectionAttributes();
  1169.         return true;
  1170.     }
  1171.  
  1172.     return false;
  1173.     }
  1174.  
  1175.     public boolean handleKeyword(String keyword, int parameter)
  1176.     {
  1177.     boolean booleanParameter = (parameter != 0);
  1178.     
  1179.     if (keyword.equals("fc"))
  1180.         keyword = "cf"; /* whatEVER, dude. */
  1181.     
  1182.     if (keyword.equals("f")) {
  1183.         parserState.put(keyword, new Integer(parameter));
  1184.         return true;
  1185.     }
  1186.     if (keyword.equals("cf")) {
  1187.         parserState.put(keyword, new Integer(parameter));
  1188.         return true;
  1189.     }
  1190.  
  1191.     {
  1192.         Object item = straightforwardAttributes.get(keyword);
  1193.         if (item != null) {
  1194.             RTFAttribute attr = (RTFAttribute)item;
  1195.         boolean ok;
  1196.         
  1197.         switch(attr.domain()) {
  1198.           case RTFAttribute.D_CHARACTER:
  1199.             ok = attr.set(characterAttributes, parameter);
  1200.             break;
  1201.           case RTFAttribute.D_PARAGRAPH:
  1202.             ok = attr.set(paragraphAttributes, parameter);
  1203.             break;
  1204.           case RTFAttribute.D_SECTION:
  1205.             ok = attr.set(sectionAttributes, parameter);
  1206.             break;
  1207.           case RTFAttribute.D_META:
  1208.             mockery.backing = parserState;
  1209.             ok = attr.set(mockery, parameter);
  1210.             mockery.backing = null;
  1211.             break;
  1212.           case RTFAttribute.D_DOCUMENT:
  1213.             ok = attr.set(documentAttributes, parameter);
  1214.             break;
  1215.           default:
  1216.             /* should never happen */
  1217.             ok = false;
  1218.             break;
  1219.         }
  1220.         if (ok)
  1221.             return true;
  1222.         }
  1223.     }
  1224.  
  1225.     if (keyword.equals("fs")) {
  1226.         StyleConstants.setFontSize(characterAttributes, (int)(parameter / 2));
  1227.         return true;
  1228.     }
  1229.  
  1230.     /* TODO: superscript/subscript */
  1231.     
  1232.     if (keyword.equals("sl")) {
  1233.         if (parameter == 1000) {  /* magic value! */
  1234.         characterAttributes.removeAttribute(StyleConstants.LineSpacing);
  1235.         } else {
  1236.         /* TODO: The RTF sl attribute has special meaning if it's
  1237.            negative. Make sure that SwingText has the same special
  1238.            meaning, or find a way to imitate that. When SwingText
  1239.            handles this, also recognize the slmult keyword. */
  1240.         StyleConstants.setLineSpacing(characterAttributes,
  1241.                           parameter / 20f);
  1242.         }
  1243.         return true;
  1244.     }
  1245.     
  1246.     /* TODO: Other kinds of underlining */
  1247.     
  1248.     if (keyword.equals("tx") || keyword.equals("tb")) {
  1249.         float tabPosition = ((float)parameter) / 20f;
  1250.         int tabAlignment, tabLeader;
  1251.         Number item;
  1252.         
  1253.         tabAlignment = TabStop.ALIGN_LEFT;
  1254.         item = (Number)(parserState.get("tab_alignment"));
  1255.         if (item != null)
  1256.         tabAlignment = item.intValue();
  1257.         tabLeader = TabStop.LEAD_NONE;
  1258.         item = (Number)(parserState.get("tab_leader"));
  1259.         if (item != null)
  1260.         tabLeader = item.intValue();
  1261.         if (keyword.equals("tb"))
  1262.         tabAlignment = TabStop.ALIGN_BAR;
  1263.         
  1264.         parserState.remove("tab_alignment");
  1265.         parserState.remove("tab_leader");
  1266.         
  1267.         TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
  1268.         Dictionary tabs;
  1269.         Integer stopCount;
  1270.         
  1271.         tabs = (Dictionary)parserState.get("_tabs");
  1272.         if (tabs == null) {
  1273.         tabs = new Hashtable();
  1274.         parserState.put("_tabs", tabs);
  1275.         stopCount = new Integer(1);
  1276.         } else {
  1277.         stopCount = (Integer)tabs.get("stop count");
  1278.         stopCount = new Integer(1 + stopCount.intValue());
  1279.         }
  1280.         tabs.put(stopCount, newStop);
  1281.         tabs.put("stop count", stopCount);
  1282.         parserState.remove("_tabs_immutable");
  1283.         
  1284.         return true;
  1285.     }
  1286.  
  1287.     if (keyword.equals("s") &&
  1288.         paragraphStyles != null) {
  1289.         parserState.put("paragraphStyle", paragraphStyles[parameter]);
  1290.         return true;
  1291.     }
  1292.  
  1293.     if (keyword.equals("cs") &&
  1294.         characterStyles != null) {
  1295.         parserState.put("characterStyle", characterStyles[parameter]);
  1296.         return true;
  1297.     }
  1298.  
  1299.     if (keyword.equals("ds") &&
  1300.         sectionStyles != null) {
  1301.         parserState.put("sectionStyle", sectionStyles[parameter]);
  1302.         return true;
  1303.     }
  1304.  
  1305.     return false;
  1306.     }
  1307.  
  1308.     /** Returns a new MutableAttributeSet containing the
  1309.      *  default character attributes */
  1310.     protected MutableAttributeSet rootCharacterAttributes()
  1311.     {
  1312.     MutableAttributeSet set = new SimpleAttributeSet();
  1313.     
  1314.     /* TODO: default font */
  1315.     
  1316.     StyleConstants.setItalic(set, false);
  1317.     StyleConstants.setBold(set, false);
  1318.     StyleConstants.setUnderline(set, false);
  1319.     StyleConstants.setForeground(set, defaultColor());
  1320.  
  1321.     return set;
  1322.     }
  1323.  
  1324.     /** Returns a new MutableAttributeSet containing the
  1325.      *  default paragraph attributes */
  1326.     protected MutableAttributeSet rootParagraphAttributes()
  1327.     {
  1328.     MutableAttributeSet set = new SimpleAttributeSet();
  1329.     
  1330.     StyleConstants.setLeftIndent(set, 0f);
  1331.     StyleConstants.setRightIndent(set, 0f);
  1332.     StyleConstants.setFirstLineIndent(set, 0f);
  1333.     
  1334.     /* TODO: what should this be, really? */
  1335.     set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
  1336.     
  1337.     return set;
  1338.     }
  1339.  
  1340.     /** Returns a new MutableAttributeSet containing the
  1341.      *  default section attributes */
  1342.     protected MutableAttributeSet rootSectionAttributes()
  1343.     {
  1344.         MutableAttributeSet set = new SimpleAttributeSet();
  1345.  
  1346.     return set;
  1347.     }
  1348.  
  1349.     /**
  1350.      * Calculates the current text (character) attributes in a form suitable
  1351.      * for SwingText from the current parser state.
  1352.      *
  1353.      * @returns a new MutableAttributeSet containing the text attributes.
  1354.      */
  1355.     MutableAttributeSet currentTextAttributes()
  1356.     {
  1357.     MutableAttributeSet attributes =
  1358.         new SimpleAttributeSet(characterAttributes);
  1359.     Integer fontnum;
  1360.     Integer stateItem;
  1361.  
  1362.     /* figure out the font name */
  1363.     /* TODO: catch exceptions for undefined attributes,
  1364.        bad font indices, etc.? (as it stands, it is the caller's
  1365.        job to clean up after corrupt RTF) */
  1366.     fontnum = (Integer)parserState.get("f");
  1367.     /* note setFontFamily() can not handle a null font */
  1368.     String fontFamily;
  1369.     if (fontnum != null)
  1370.         fontFamily = (String)fontTable.get(fontnum);
  1371.     else
  1372.         fontFamily = null;
  1373.     if (fontFamily != null)
  1374.         StyleConstants.setFontFamily(attributes, fontFamily);
  1375.     else
  1376.         attributes.removeAttribute(StyleConstants.FontFamily);
  1377.     
  1378.     if (colorTable != null) {
  1379.         stateItem = (Integer)parserState.get("cf");
  1380.         if (stateItem != null) {
  1381.         Color fg = colorTable[stateItem.intValue()];
  1382.         StyleConstants.setForeground(attributes, fg);
  1383.         } else {
  1384.         /* AttributeSet dies if you set a value to null */
  1385.         attributes.removeAttribute(StyleConstants.Foreground);
  1386.         }
  1387.     }
  1388.     
  1389.     if (colorTable != null) {
  1390.         stateItem = (Integer)parserState.get("cb");
  1391.         if (stateItem != null) {
  1392.         Color bg = colorTable[stateItem.intValue()];
  1393.         attributes.addAttribute(StyleConstants.Background,
  1394.                     bg);
  1395.         } else {
  1396.         /* AttributeSet dies if you set a value to null */
  1397.         attributes.removeAttribute(StyleConstants.Background);
  1398.         }
  1399.     }
  1400.     
  1401.     Style characterStyle = (Style)parserState.get("characterStyle");
  1402.     if (characterStyle != null)
  1403.         attributes.setResolveParent(characterStyle);
  1404.  
  1405.     /* Other attributes are maintained directly in "attributes" */
  1406.     
  1407.     return attributes;
  1408.     }
  1409.  
  1410.     /**
  1411.      * Calculates the current paragraph attributes (with keys
  1412.      * as given in StyleConstants) from the current parser state.
  1413.      *
  1414.      * @returns a newly created MutableAttributeSet. 
  1415.      * @see StyleConstants
  1416.      */
  1417.     MutableAttributeSet currentParagraphAttributes()
  1418.     {
  1419.     /* NB if there were a mutableCopy() method we should use it */
  1420.     MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
  1421.     
  1422.     Integer stateItem;
  1423.     
  1424.     /*** Tab stops ***/
  1425.     TabStop tabs[];
  1426.     
  1427.     tabs = (TabStop[])parserState.get("_tabs_immutable");
  1428.     if (tabs == null) {
  1429.         Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
  1430.         if (workingTabs != null) {
  1431.         int count = ((Integer)workingTabs.get("stop count")).intValue();
  1432.         tabs = new TabStop[count];
  1433.         for (int ix = 1; ix <= count; ix ++)
  1434.             tabs[ix-1] = (TabStop)workingTabs.get(new Integer(ix));
  1435.         parserState.put("_tabs_immutable", tabs);
  1436.         }
  1437.     }
  1438.     if (tabs != null)
  1439.         bld.addAttribute(Constants.Tabs, tabs);
  1440.  
  1441.     Style paragraphStyle = (Style)parserState.get("paragraphStyle");
  1442.     if (paragraphStyle != null)
  1443.         bld.setResolveParent(paragraphStyle);
  1444.     
  1445.     return bld;
  1446.     }
  1447.     
  1448.     /**
  1449.      * Calculates the current section attributes
  1450.      * from the current parser state.
  1451.      *
  1452.      * @returns a newly created MutableAttributeSet. 
  1453.      */
  1454.     public AttributeSet currentSectionAttributes()
  1455.     {
  1456.     MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
  1457.  
  1458.     Style sectionStyle = (Style)parserState.get("sectionStyle");
  1459.     if (sectionStyle != null)
  1460.         attributes.setResolveParent(sectionStyle);
  1461.  
  1462.     return attributes;
  1463.     }
  1464.  
  1465.     /** Resets the filter's internal notion of the current character
  1466.      *  attributes to their default values. Invoked to handle the
  1467.      *  \plain keyword. */
  1468.     protected void resetCharacterAttributes()
  1469.     {
  1470.     handleKeyword("f", 0);
  1471.     handleKeyword("cf", 0);
  1472.     
  1473.     handleKeyword("fs", 24);  /* 12 pt. */
  1474.     
  1475.     Enumeration attributes = straightforwardAttributes.elements();
  1476.     while(attributes.hasMoreElements()) {
  1477.         RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1478.         if (attr.domain() == RTFAttribute.D_CHARACTER) 
  1479.             attr.setDefault(characterAttributes);
  1480.     }
  1481.     
  1482.     handleKeyword("sl", 1000);
  1483.  
  1484.     parserState.remove("characterStyle");
  1485.     }
  1486.  
  1487.     /** Resets the filter's internal notion of the current paragraph's
  1488.      *  attributes to their default values. Invoked to handle the
  1489.      *  \pard keyword. */
  1490.     protected void resetParagraphAttributes()
  1491.     {
  1492.     parserState.remove("_tabs");
  1493.     parserState.remove("_tabs_immutable");
  1494.     parserState.remove("paragraphStyle");
  1495.     
  1496.     StyleConstants.setAlignment(paragraphAttributes, 
  1497.                     StyleConstants.ALIGN_LEFT);
  1498.  
  1499.     Enumeration attributes = straightforwardAttributes.elements();
  1500.     while(attributes.hasMoreElements()) {
  1501.         RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1502.         if (attr.domain() == RTFAttribute.D_PARAGRAPH) 
  1503.             attr.setDefault(characterAttributes);
  1504.     }
  1505.     }
  1506.     
  1507.     /** Resets the filter's internal notion of the current section's
  1508.      *  attributes to their default values. Invoked to handle the
  1509.      *  \sectd keyword. */
  1510.     protected void resetSectionAttributes()
  1511.     {
  1512.     Enumeration attributes = straightforwardAttributes.elements();
  1513.     while(attributes.hasMoreElements()) {
  1514.         RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1515.         if (attr.domain() == RTFAttribute.D_SECTION) 
  1516.             attr.setDefault(characterAttributes);
  1517.     }
  1518.  
  1519.     parserState.remove("sectionStyle");
  1520.     }
  1521. }
  1522.  
  1523. /** RTFReader.TextHandlingDestination provides basic text handling
  1524.  *  functionality. Subclasses must implement: <dl>
  1525.  *  <dt>deliverText()<dd>to handle a run of text with the same
  1526.  *                       attributes
  1527.  *  <dt>finishParagraph()<dd>to end the current paragraph and
  1528.  *                           set the paragraph's attributes
  1529.  *  <dt>endSection()<dd>to end the current section
  1530.  *  </dl>
  1531.  */
  1532. abstract class TextHandlingDestination
  1533.     extends AttributeTrackingDestination
  1534.     implements Destination
  1535. {
  1536.     /** <code>true</code> if the reader has not just finished
  1537.      *  a paragraph; false upon startup */
  1538.     boolean inParagraph;
  1539.     
  1540.     public TextHandlingDestination()
  1541.     {
  1542.         super();
  1543.     inParagraph = false;
  1544.     }
  1545.  
  1546.     public void handleText(String text)
  1547.     {
  1548.     if (! inParagraph)
  1549.         beginParagraph();
  1550.     
  1551.     deliverText(text, currentTextAttributes());
  1552.     }
  1553.  
  1554.     abstract void deliverText(String text, AttributeSet characterAttributes);
  1555.  
  1556.     public void close()
  1557.     {
  1558.     if (inParagraph)
  1559.         endParagraph();
  1560.  
  1561.     super.close();
  1562.     }
  1563.  
  1564.     public boolean handleKeyword(String keyword)
  1565.     {
  1566.     if (keyword.equals("\r") || keyword.equals("\n")) {
  1567.         keyword = "par";
  1568.     }
  1569.     
  1570.     if (keyword.equals("par")) {
  1571. //        warnings.println("Ending paragraph.");
  1572.         endParagraph();
  1573.         return true;
  1574.     }
  1575.  
  1576.     if (keyword.equals("sect")) {
  1577. //        warnings.println("Ending section.");
  1578.         endSection();
  1579.         return true;
  1580.     }
  1581.     
  1582.     return super.handleKeyword(keyword);
  1583.     }
  1584.  
  1585.     protected void beginParagraph()
  1586.     {
  1587.     inParagraph = true;
  1588.     }
  1589.     
  1590.     protected void endParagraph()
  1591.     {
  1592.     AttributeSet pgfAttributes = currentParagraphAttributes();
  1593.     AttributeSet chrAttributes = currentTextAttributes();
  1594.     finishParagraph(pgfAttributes, chrAttributes);
  1595.     inParagraph = false;
  1596.     }
  1597.  
  1598.     abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
  1599.  
  1600.     abstract void endSection();
  1601. }
  1602.  
  1603. /** RTFReader.DocumentDestination is a concrete subclass of
  1604.  *  TextHandlingDestination which appends the text to the
  1605.  *  StyledDocument given by the <code>target</code> ivar of the
  1606.  *  containing RTFReader.
  1607.  */
  1608. class DocumentDestination
  1609.     extends TextHandlingDestination
  1610.     implements Destination
  1611. {
  1612.     public void deliverText(String text, AttributeSet characterAttributes)
  1613.     {
  1614.     try {
  1615.         target.insertString(target.getLength(),
  1616.                 text,
  1617.                 currentTextAttributes());
  1618.     } catch (BadLocationException ble) {
  1619.         /* This shouldn't be able to happen, of course */
  1620.         /* TODO is InternalError the correct error to throw? */
  1621.         throw new InternalError(ble.getMessage());
  1622.     }
  1623.     }
  1624.  
  1625.     public void finishParagraph(AttributeSet pgfAttributes,
  1626.                 AttributeSet chrAttributes)
  1627.     {
  1628.     int pgfEndPosition = target.getLength();
  1629.     try {
  1630.         target.insertString(pgfEndPosition, "\n", chrAttributes);
  1631.         target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
  1632.     } catch (BadLocationException ble) {
  1633.         /* This shouldn't be able to happen, of course */
  1634.         /* TODO is InternalError the correct error to throw? */
  1635.         throw new InternalError(ble.getMessage());
  1636.     }
  1637.     }
  1638.  
  1639.     public void endSection()
  1640.     {
  1641.         /* If we implemented sections, we'd end 'em here */
  1642.     }
  1643. }    
  1644.  
  1645. }
  1646.  
  1647.  
  1648.